﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.NetworkInformation;
using Golem.Indexer.Data;
using System.Threading;
using Golem.Indexer.Indexers;

namespace Golem.Indexer.Search
{
    internal class Search : ISearch
    {
        private struct NetworkInfo
        {
            public String Ip { get; set; } 
            public String Mask { get; set; }
        }

        private Dictionary<String, MachineDescriptor> machines = new Dictionary<string, MachineDescriptor>();
        private List<NetworkInfo> networks = new List<NetworkInfo>();
        private int delay = 50;
        private int computerCount = 0;
        private int onlineComputerCount = 0;
        private int pingedComputerCount = 0;

        public event SearchHandler SearchScanCompleted;
        public event MachineHandler MachineAlive;
        public event MachineHandler MachinePingCompleted;

        public int ComputerCounts { get { return computerCount; } }
        public int OnlineComputerCount { get { return onlineComputerCount; } }
        public int PingedComputersCount { get { return pingedComputerCount; } }

        public bool AreUnPingedComputers { get { return this.machines.Count > 0; } }

        public Search() : this(100)
        {

        }

        public Search(int delay)
        {
            this.delay = delay;
        }

        public void AddNetwork(String ip, String mask)
        {
            this.networks.Add(new NetworkInfo() { Ip = ip, Mask = mask });
        }

        public int ReloadScanNetworks()
        {
            this.machines.Clear();
            this.computerCount = 0;

            foreach (var network in this.networks)
            {
                int count = this.AddNetworkToScan(network.Ip, network.Mask);
                this.computerCount += count;
            }

            return computerCount;
        }

        public int AddNetworkToScan(string ip, string mask)
        {
            var networkIP = ip.GetNetworkIp(mask);

            var ipBlock = networkIP.Split('.');
            var iipBlock = new int[4];

            for (int i = 0; i < ipBlock.Length; i++)
                iipBlock[i] = Convert.ToInt32(ipBlock[i]);

            var hostCount = mask.GetHostCountByMask();
            
            iipBlock[3]--;  //fix for first ip address

            for (int i = 0; i < hostCount; i++)
            {
                iipBlock[3]++;

                if (iipBlock[3] == 256)
                {
                    iipBlock[3] = 0;
                    iipBlock[2]++;
                }

                if (iipBlock[2] == 256)
                {
                    iipBlock[2] = 0;
                    iipBlock[1]++;
                }

                if (iipBlock[1] == 256)
                {
                    iipBlock[1] = 0;
                    iipBlock[0]++;
                }

                ipBlock = new String[] { iipBlock[0].ToString(), iipBlock[1].ToString(), iipBlock[2].ToString(), iipBlock[3].ToString() };
                string generatedIp = ipBlock.ToIP();

                if (generatedIp.EndsWith(".0") || generatedIp.EndsWith(".255"))
                    continue;

                machines.Add(generatedIp, new MachineDescriptor(generatedIp));
            }

            return hostCount;
        }

        public void ScanNetworks()
        {
            while (machines.Count > 0)
            {
                var machine = machines.Values.ElementAt(0);

                lock (this.machines)
                {
                    machines.Remove(machine.IP);
                }

                Ping ping = new Ping();

                ping.PingCompleted += new PingCompletedEventHandler(ping_PingCompleted);
                ping.SendAsync(machine.IP, 250, machine);

                Thread.Sleep(this.delay);
            }

            if (SearchScanCompleted != null)
                SearchScanCompleted();
        }

        void ping_PingCompleted(object sender, PingCompletedEventArgs e)
        {
            var machine = (MachineDescriptor)e.UserState;
            
            MachinePinged(e.Reply, machine);
        }

        private void MachinePinged(PingReply reply, MachineDescriptor machine)
        {
            machine.IsOnline = reply.Status == IPStatus.Success;
            machine.LastUpdateTime = DateTime.Now;

            try
            {
                pingedComputerCount++;

                if (machine.IsOnline)
                {
                    this.onlineComputerCount++;
                    machine.Hostname = System.Net.Dns.GetHostEntry(machine.IP).HostName;    //Tato radka muze spadnout, za ni nic nepsat!
                }
            }
            catch { }

            if (this.MachinePingCompleted != null)
                this.MachinePingCompleted(machine);

            if (machine.IsOnline && this.MachineAlive != null)
            {
                this.MachineAlive(machine);
            }
        }
    }
}
